import java.awt.*;
import javax.swing.*;
import java.awt.event.*;

/**
 * Creates a GUI for a Client that displays provided input and captures user input
 * for outputing to the ClientHandler.
 *
 * @author BluJaded Team
 * @version (see Sourceforge.net)
 */
public class ClientGui extends JFrame implements MouseListener, KeyListener, WindowListener, ClientUiInterface {

    // TextArea for the display of outputed messages
    private JTextArea outputT;

    // TextField for the input of messages to be sent
    private JTextField inputT;

    // label to display the current mode of operation
    private JLabel modeLabel;

    // reference for the Client object assoiated to this ClientGUI
    private Client client;

    // TextArea for the display of a list of currently connected Clients
    private JTextArea clientsT;

    // boolean used to show that the client is attempting a whisper using the /w <nickname> <message> command
    private boolean attemptWisp;

    // boolean used to show if the result from an attempt to whisper using the /w <nickname> <message> command has been received
    private boolean resultRecieved;

    /**
     * Filename of the icon for the GUI.
     */
    private static final String iconFilename = "jadetalk gui.gif";

    /**
     * Creates the GUI for a Client object provided.
     *
     * @param title     the title of the GUI frame to be created.
     * @param myClient  the Client object assosiated with this GUI
     */
    public ClientGui(String title, Client myClient) {
        super(title);
        client = myClient;
        this.addWindowListener(this);
        Container contentPane = this.getContentPane();
        contentPane.setLayout (new BorderLayout ());
        Container controlPanel = new JPanel();
        controlPanel.setLayout(new BorderLayout());
        Container inputPanel = new JPanel();
        inputPanel.setLayout(new BorderLayout());
        outputT = new JTextArea (40,33);
        outputT.setLineWrap (true);
        outputT.setWrapStyleWord (true);
        JScrollPane outputScrollPane = new JScrollPane(outputT);
        clientsT = new JTextArea (10,6);
        clientsT.setLineWrap (true);
        JScrollPane clientsScrollPane = new JScrollPane(clientsT);
        Container topPane = new JPanel();
        topPane.setLayout (new BoxLayout(topPane, BoxLayout.Y_AXIS));
        topPane.add(new JLabel (" "));
        ImageIcon icon = createImageIcon();
        JLabel label = new JLabel(icon);
        topPane.add(label);
        topPane.add(new JLabel ("           BluJaded Team  "));
        topPane.add(new JLabel ("                      (2006)"));
        topPane.add(new JLabel (" "));
        topPane.add(new JLabel("          Clients Connected:"));
        topPane.add(new JLabel (" "));
        Container midPane = new JPanel();
        midPane.setLayout (new BorderLayout ());
        midPane.add(new JLabel ("     "),BorderLayout.WEST);
        midPane.add(new JLabel ("     "),BorderLayout.EAST);
        midPane.add(clientsScrollPane,BorderLayout.CENTER);
        Container bottomPane = new JPanel();
        bottomPane.setLayout (new BoxLayout(bottomPane, BoxLayout.Y_AXIS));
        bottomPane.add(new JLabel (" "));
        controlPanel.add (topPane, BorderLayout.NORTH);
        controlPanel.add (midPane, BorderLayout.CENTER);
        controlPanel.add (bottomPane, BorderLayout.SOUTH);
        modeLabel = new JLabel("** Broadcast Mode - You are now Broadcasting to all Clients **");
        modeLabel.setHorizontalAlignment(0);
        inputPanel.add (modeLabel, BorderLayout.CENTER);
        contentPane.add (outputScrollPane, BorderLayout.CENTER);
        contentPane.add(controlPanel,BorderLayout.EAST);
        outputT.setEditable (false);
        clientsT.setEditable(false);
        inputT = new JTextField ();
        inputT.addKeyListener(this);
        inputPanel.add (inputT, BorderLayout.SOUTH);
        contentPane.add (inputPanel, BorderLayout.SOUTH);
        pack ();
        setVisible (true);
        inputT.requestFocus ();
    }

    /**
     * Displays the provided message in the GUI
     * Also checks and updates nickname and the list of connected clients as neccesary.
     *
     * @param msg   Message to be displayed in the GUI
     */
    public void displayMsg(Message msg){
        // if the user has changed their Nickname
        if (!msg.getNick().equals(client.getNickname())){
            // save the new nickname to the clients nickname variable
            client.setNickname(msg.getNick());
        }
        String line = processMsg(msg);

        // if the message is not empty
        if (!line.equals("")){
            // outputs the message to the output area of the GUI
            outputT.append (line + "\n");
            outputT.setCaretPosition( outputT.getDocument().getLength() );
        }
        // updates the list of connected clients to the GUI
        clientsT.setText(msg.getConnected());
    }

    /**
     * Processes the message and gives an output of what the message should show
     * @param msg   Message to be processed
     * @return      String representation of Message
     */
    private String processMsg(Message msg) {
        String line;
        //if the message is marked as a whisper
        if (msg.getWisp()){
            // outputs what they have been whispered and by who to the output area of the GUI
            line = (msg.getMsgFrom()+ " Whispers to you - "+ msg.getContent());
        }
        else{
            // if its a normal message
            // outputs the message to the output area of the GUI
            line = msg.getContent();
        }

        // if the user is attempting to whisper using the /w <nickname> <message> command
        if (attemptWisp){
            // if the message has been marked as a result of that attempt
            if (msg.getMsgFrom().equals(client.getNickname())){
                // sets the attemptWisp value to that of the messages WispSuccess
                attemptWisp = msg.getWispSuccess();
                // set the resultRecieved flag to true such that the client may procceed with the attempted whisper
                resultRecieved = true;
            }
        }
        else{
            // if the Client has entered whisper mode with a /w <nickname> command successfully
            if (msg.getWispSuccess()){
                // updates the modeLabel
                modeLabel.setText("** Whisper Mode - You are Whispering to: " + msg.getMsgFrom() + " **");
            }
            else{
                // if the Client has returned to Broadcast mode due to their whisper target leaving the server
                if (msg.getMsgFrom().equals(client.getNickname())){
                    // updates the modeLabel
                    modeLabel.setText("** Broadcast Mode - You are now Broadcasting to all Clients **");
                }
            }
        }
        return line;
    }


    /**
     * Hides the input region of the GUI preventing further input
     * and states that connection with server was lost.
     */
    public void disconnect(){
        // hide the input region of the GUI
        inputT.setVisible(false);
        // update the GUI to reflect changes
        validate();
        // inform the user that they have been disconnected
        outputT.append("** Connection to the Server Lost **\n");
    }

    public void mouseClicked(MouseEvent e) {}

    /**
     * KeyPressed Event occurs when a key is pressed on the users keyboard.
     *
     * @param  pushedKey    the KeyEvent object assosiated with the event.
     **/
    public void keyPressed(KeyEvent pushedKey) {

        int keyCode = pushedKey.getKeyCode();

        // if the key pressed was Eneter
        if (keyCode==KeyEvent.VK_ENTER) {

            // splits the inputted text into its composite words and adds these words to a string array.
            // A word is anything surrounded by whitespaces.
            // The first 2 words are placed in the array and the third element of the array holds
            // The remainder of the inputed text
            // If the string begins with one or more whitespaces then the first element will hold an empty string
            String[] inpt = inputT.getText().split("\\s+",3);

            // if the inputted text was an empty string then inpt will be an empty array
            // if inpt is empty
            if (inpt.length == 0){
                // makes inpt an array with a single empty string so that it can be proccessed below
                inpt = new String[]{""};
            }

            //if the first element of the inpt array is an empty string and there is more than one element in the array
            if (inpt[0].equals("") && inpt.length>1) {

                //creates a new string array with one less element than the inpt array
                String[] inptcopy= new String[inpt.length-1];

                //copies each but the first element of inpt into the new string array
                for(int i = 1;i < inpt.length;i++){
                    inptcopy[i - 1] = inpt[i];
                }

                //passes the new shortened array to the inpt array
                inpt=inptcopy;

            }

            //if there was a message in the textField
            if (!inpt[0].equals("")) {
                // If message might be a command
                if(inpt[0].startsWith("/")) {
                    processCommand(inpt);
                }
                else {
                    // if the read line is a normal message
                    sendNormalTextMessage();
                }
            }
            else {
                // No message in textField so reset textField to nothing
                inputT.setText("");
            }
        }
    }

    /**
     * Process what may be a command message
     * @param inpt  The user inputted String as an array of words
     */
    private void processCommand(String[] inpt) {

        // if message only has one word
        if(inpt.length == 1) {
            // Take word from input
            String word = inpt[0];
            // Process single word to see if it is a command
            processSingleWordCommand(word);
        }
        //if the input is more than a single word and the second word is not an empty string
        else if (inpt.length>=2 && !inpt[1].equals("")){
            Message sndMsg = new Message();
            //if the first word of the message is a nickname change command
            if (inpt[0].equals("/nick")){
                // Get the propoed message from the user input
                String proposedNickname = inpt[1];
                // Process the nickname
                processNickname(proposedNickname);
            }
            else{
                // if the first word of the message is a whisper command
                if(inpt[0].equals("/w")){
                    // Get nickname of Client that this Client is trying to whisper to
                    String whisperTo = inpt[1];

                    // if the provided nickname is more then 10 characters
                    if (whisperTo.length()>10){
                        //set the whisperTo variable to the first 10 characters of the provided nickname
                        whisperTo = whisperTo.substring(0,10);
                    }
                    // if the specified nickname is not this Clients Nickname
                    if (!whisperTo.toLowerCase().equals(client.getNickname().toLowerCase())){
                        // if the message is more than 2 words
                        if (inpt.length==2){
                            // if the message was a whisper command and nickname with no further text
                            // set the Messages MsgTo variable to the provided nickname, acting as a whisper command
                            // to the ClientHandler
                            sndMsg.setMsgTo(whisperTo);
                            //sends the message to the ClientHandler
                            client.outputMsg(sndMsg);
                        }
                        else{
                            //set the attemptWisp flag to denote that the client is attempting a whisper
                            attemptWisp=true;

                            // sets the Messages wisp variable to true which is used to suppress mode change
                            // conformation
                            sndMsg.setWisp(true);
                            // set the Messages MsgTo variable to the provided nickname, acting as a whisper command
                            // to the ClientHandler
                            sndMsg.setMsgTo(whisperTo);
                            //sends the message to the ClientHandler
                            client.outputMsg(sndMsg);

                            int timer=0;
                            // while the result from an attempt to whisper using the /w <nickname> <message> command
                            // has not been recieved
                            while (!resultRecieved){
                                // increments the timer int
                                timer++;
                                // if the timer int has reached its timeout value
                                if (timer>1300000000){
                                    //terminate this while loop
                                    resultRecieved=true;
                                    //mark the whisper attempt as having failed
                                    attemptWisp=false;

                                    // make sure that if whisper mode was achieved, and only conformation was never
                                    // recieved, that the client is returned to Broadcast Mode

                                    //creates a new Message object
                                    Message lastmsg = new Message();
                                    // sets the Messages wisp variable to true which is used to suppress mode change
                                    // conformation
                                    lastmsg.setWisp(true);
                                    // make the Message a end whisper command
                                    lastmsg.setEndWisp(true);
                                    // sends the message to the ClientHandler
                                    client.outputMsg( lastmsg );

                                    // inform the client that a problem has occured
                                    outputT.append("** The attempt to whisper " +whisperTo + " caused an Unknown Exception (JTECode 1) **" +
                                            "\n** Please refer to documentation for possible causes of this exception **\n" +
                                            "** Your chat session and all other users have been unaffected **");
                                }
                            }

                            //resets the resultRecieved flag for re-use
                            resultRecieved=false;

                            //if the attempt succeeded (ie the message returned has caused attempt wisp to still be true)
                            if (attemptWisp){

                                //reset the attemptWisp flag for re-use
                                attemptWisp=false;

                                //creates a new Message object
                                Message nxtmsg = new Message();
                                //set the content of this message to the provided message (third and proceeding words)
                                nxtmsg.setContent(inpt[2]);
                                //sends the message to the ClientHandler
                                client.outputMsg(nxtmsg);

                                //creates a new Message object
                                Message lastmsg = new Message();
                                //sets the Messages wisp variable to true which is used to suppress mode change conformation
                                lastmsg.setWisp(true);
                                //make the Message a end whisper command
                                lastmsg.setEndWisp(true);
                                //sends the message to the ClientHandler
                                client.outputMsg( lastmsg );
                            }
                        }
                    }

                    //clear the input field for new message
                    inputT.setText("");
                }
                else{
                    //if the read line is a normal message
                    sendNormalTextMessage();
                }
            }
        } // end of if length >= 2
        else{
            //if the read line is a normal message
            sendNormalTextMessage();
        }
    }

    /**
     * Makes the nickname the correct length and sends a Message to request the changing of this clients nickname
     * @param proposedNickname  The nickname that this Client wants to change to
     */
    private void processNickname(String proposedNickname) {
        Message sndMsg = new Message();
        //if the provided nickname(the next word) is less than 10 characters
        if (proposedNickname.length()<10){
            //set the messages nickname variable to the provided nickname
            sndMsg.setNick(proposedNickname);
        }
        else{
            //set the messages nickname variable to the first 10 characters of the provided nickname
            sndMsg.setNick(proposedNickname.substring(0,10));
        }

        //if the provided nickname is not this Clients Nickname
        if (!sndMsg.getNick().toLowerCase().equals(client.getNickname().toLowerCase())){
            // Write message to outputstream of client
            client.writeOutput(sndMsg);
        }
        //clear the input field for new message
        inputT.setText("");
    }

    /**
     * Processes a word from user input which may be a command
     * @param word  First word of user input
     */
    private void processSingleWordCommand(String word) {
        Message sndMsg = new Message();
        //if the word is a list command
        if (word.equals("/list")) {
            //make the Message a list command
            sndMsg.setListClients(true);
            //sends the message to the ClientHandler
            client.outputMsg( sndMsg );
            //clear the input field for new message
            inputT.setText("");
        }
        //if the word is a quit command
        else if(word.equals("/quit")) {
            // Cleanly quit client
            quitClient();
        }
        //if the word is a quitserver command
        else if(word.equals("/quitserver")) {
            //make the Message a quitserver command
            sndMsg.setServerQuit(true);
            //sends the message to the ClientHandler
            client.outputMsg( sndMsg );
            inputT.setText("");
        }
        // if the word is a return to broadcast command
        else if (word.equals("/b")){
            sndMsg.setEndWisp(true);
            //sends the message to the ClientHandler
            client.outputMsg( sndMsg );
            modeLabel.setText("** Broadcast Mode - You are now Broadcasting to all Clients **");
            inputT.setText("");
        }
        else if (word.equals("/help")){
            sndMsg.setHelp(true);
            // sends the message to the ClientHandler
            client.outputMsg( sndMsg );
            inputT.setText("");
        }
        else {
            //if the read line is a normal message
            sendNormalTextMessage();
        }
    }

    /**
     * Send a normal message containing just text
     */
    private void sendNormalTextMessage() {
        Message sndMsg = new Message();
        //set the Message content to the inputed text
        sndMsg.setContent(inputT.getText());
        //sends the message to the ClientHandler
        client.outputMsg( sndMsg );
        //clear the input field for new message
        inputT.setText("");
    }


    public void keyReleased(KeyEvent e) {}

    public void keyTyped(KeyEvent e) {}

    public void mouseEntered(MouseEvent e) {}

    public void mouseExited(MouseEvent e) {}

    public void mousePressed(MouseEvent e) {}

    public void mouseReleased(MouseEvent e)  {}

    public void windowActivated(WindowEvent e)  {}

    public void windowClosed(WindowEvent e)  {}

    /**
     * windowClosing Event occurs when the window is closed by the user or program.
     *
     * @param      e    the WindowEvent object assosiated with the event.
     **/
    public void windowClosing(WindowEvent e)  {
        // if the thread is still running
        if (client.stillRunning()){
            // Cleanly quit client
            quitClient();
        }
        else{
            //closes the window by disposing of the frame.
            dispose ();
        }
    }

    /**
     * Quits the client cleanly
     */
    public void quitClient()
    {
        // terminates the thread cleanly
        client.terminateClient();
        // creates a new Message
        Message sndMsg = new Message();
        // makes it a quit command
        sndMsg.setQuit(true);
        // outputs the message to the ClientHandler
        client.outputMsg( sndMsg );
        // closes the window by disposing of the frame.
        dispose ();

    }

    public void windowDeactivated(WindowEvent e)  {}

    public void windowDeiconified(WindowEvent e)  {}

    public void windowIconified(WindowEvent e)  {}

    public void windowOpened(WindowEvent e)  {}

    /**
     * Creates an image icon for the image with the filemane provided in the programs directory.
     *
     * @return returns an ImageIcon, or null if the path was invalid
     */
    private static ImageIcon createImageIcon() {

        // creates a URL to the specified filename in the programs directory
        java.net.URL imgURL = ClientGui.class.getResource(iconFilename);
        // if the file was found
        if (imgURL != null) {
            // returns a new imageIcon for the image
            return new ImageIcon(imgURL, "");
        } else {
            // informs user that file was not found.
            System.out.println("Couldn't find file: " + iconFilename);
            return null;
        }
    }
}